/*
 Copyright (c) Whitemice Consulting, 2007, 2008, 2009
 Adam Tauno Williams <awilliam@whitemice.org>
 License: MIT/X11

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using Whitemice.ZOGI;

namespace Whitemice.ZOGI
{
  /// <summary>
  /// Simple class used internally to keep track of relationships between 
  /// entities and windows that need to be norified if the entity is modified.
  /// </summary>
  internal class EntityReference
  {
    protected WeakReference                      target;
    
    public EntityReference(IEntityView target)
    { this.target = new WeakReference(target); }
   
    public IEntityView Target 
    { 
      get 
      { 
        if (target.IsAlive)
          return (target.Target as IEntityView);
        Registry.UnregisterForNotification(target);
        return null;
       }
    } // end Window
  } // end EntityReference

  public static class Registry
  {   
    private static Dictionary<string, object>            registry;
    private static Dictionary<Whitemice.ZOGI.Entity,
                                List<EntityReference>>   targetList;
    private static int                                   derefCount;

    static Registry()
    {
      derefCount = 0;
      registry = new Dictionary<string, object>();
      targetList = new Dictionary<Whitemice.ZOGI.Entity,
                                  List<EntityReference>>();
      StoreObject("APP/TARGETLIST", targetList);
    } // end Init
    
    public static object GetObject(string _path)
    {
      string[]                    path;
      IDictionary<string, object>  folder;
      object                                     retVal = null;
           
      lock(registry)
      {
        path =  _path.ToUpper().Split('/');
        folder = registry;
        for(int count = 0; count < path.Length; count++)
        {
          if (count == (path.Length - 1))
          {
            /* Last component in the provided path, this is the key name */
            if (folder.ContainsKey(path[count]))
            {
              retVal = folder[path[count]];
              break;
            } else
              {
                retVal = null;
                break;
              }
          }
          if (folder.ContainsKey(path[count]))
          {
            folder = (folder[path[count]] as IDictionary<string, object>);
          } else
            {
              retVal = null;
              break;
            }
        }
      }
      return retVal;
    } // end GetObject
    
    public static void StoreObject(string _path, object _object)
    {
      string[]                                   path;
      IDictionary<string, object>                folder;
      
      lock(registry)
      {
        folder = registry;
        path = _path.ToUpper().Split('/');
        for(int count = 0; count < path.Length; count++)
        {
          if (count == (path.Length - 1))
          {
            /* Last component in the provided path, this is the key name */
            if (folder.ContainsKey(path[count]))
              folder[path[count]] = _object;
            else folder.Add(path[count], _object);
            return;
          }
          if (!(folder.ContainsKey(path[count])))
            folder.Add(path[count], new Dictionary<string, object>());
          folder = (folder[path[count]] as IDictionary<string, object>);
        } /* end for-loop-path-parts */
      } // end lock
    } // end StoreObject 
    
    /// <summary>
    /// Register an new object:entity relationship.  Any object the implements
    /// the IEntityView interface can register for notifications for any events
    /// relating to any number of entities.  To cancel the subscription to an
    /// entitie' events the UnregisterForNotification method is used.
    /// </summary>
    /// <param name="entity">
    /// A <see cref="Whitemice.ZOGI.Entity"/>
    /// </param>
    /// <param name="window">
    /// A <see cref="Gtk.Window"/>
    /// </param>
    public static void RegisterForNotification(Whitemice.ZOGI.Entity entity, IEntityView target)
    {
      lock(targetList)
      {
        if (targetList.ContainsKey(entity))
        {
          foreach(EntityReference er in targetList[entity])
          {
            // do nothing if target is already subscribed
            if (er.Target == target)
              return;
          }
          // subscribe target to entity
          targetList[entity].Add(new EntityReference(target));
          return;
        } // end if-entity-has-subscriptions
        // create a new subscription list
        targetList.Add(entity, new List<EntityReference>());
        // subscribe target to entity
        targetList[entity].Add(new EntityReference(target));
      } // end lock(targetList)
    } // end RegisterForNotification
    
    /// <summary>
    /// Unregister a window:entity relationship
    /// </summary>
    /// <param name="_entity">
    /// A <see cref="Whitemice.ZOGI.Entity"/>
    /// </param>
    /// <param name="_window">
    /// A <see cref="Gtk.Window"/>
    /// </param>
    /// <param name="_destroy">
    /// A <see cref="System.Boolean"/>
    /// </param>
    public static bool UnregisterForNotification(Whitemice.ZOGI.Entity entity, IEntityView target)
    {
      EntityReference                            relationship = null;
      bool                                       retVal = false;
      
      lock (targetList)
      {
        if (targetList.ContainsKey(entity))
        {
          foreach(EntityReference er in targetList[entity])
            if (er.Target == target)
            {
              relationship = er;     
            }
          if (relationship != null)
          {
            targetList[entity].Remove(relationship);
            derefCount++;
            CleanSubscribers();
            retVal = true;
          }
        }
      } // end lock
      return retVal;
    } // end UnregisterForNotification
    
    /// <summary>
    /// Unsubscribes the specified target from all entity event notification.
    /// </summary>
    /// <param name="target">
    /// A <see cref="System.Object"/>
    /// </param>
    /// <returns>
    /// A <see cref="System.Boolean"/>
    /// </returns>
    public static bool UnregisterForNotification(object target)
    {
      bool                                        result;
      IList<EntityReference>                      candidates;
      
      result = false;
      lock(targetList)
      {
        foreach(Entity entity in targetList.Keys)
        {
          candidates = null;
          foreach (EntityReference reference in targetList[entity])
          {
            if (reference.Target == target)
            {
              if (candidates == null)
                candidates = new List<Whitemice.ZOGI.EntityReference>();
              candidates.Add(reference);
              }
          } // end foreach-subscriber-in-subscriber-lists
          if (candidates != null)
          {
            foreach(EntityReference reference in candidates)
            {
              targetList[entity].Remove(reference);
              Console.WriteLine("Removing {0} from subscriber list.", reference.Target);
              derefCount++;
              result = true;
            }
          } // end if-candidate-found-to-remove
        } // end foreach-subscribed-entities
        candidates = null;
        CleanSubscribers();
      }
      return result;
    } // end UnregisterForNotification
    
    /// <summary>
    /// Purge the subscriber list of all targets that are no longer alive.
    /// </summary>
    private static void CleanSubscribers()
    {
      List<Entity>                                candidates;
      
      if (derefCount > 50)
      {
        candidates = new List<Entity>();
        foreach(Entity entity in targetList.Keys)
        {
          if(targetList[entity].Count == 0)
            candidates.Add(entity);
        }
        foreach(Entity candidate in candidates)
          targetList.Remove(candidate);
        candidates = null;
        derefCount = 0;
      }
    } // end CleanSubscribers
    
    /// <summary>
    /// Push the specified event to all subscribers of the specified entity
    /// </summary>
    /// <param name="entity">
    /// A <see cref="Whitemice.ZOGI.Entity"/>
    /// </param>
    /// <param name="args">
    /// A <see cref="ServerEvent"/>
    /// </param>
    internal static void Notify(Whitemice.ZOGI.Entity entity, ServerEvent args)
    {
      IEntityView                                 target;
      lock(targetList)
      {
        if (targetList.ContainsKey(entity))
        {
          foreach(EntityReference er in targetList[entity])
          {
            target = er.Target;
            // make sure target is alive
            if (target != null)
              target.ReceiveEvent(args);
          } // end foreach-target
        } // end if-entity-has-subscribers
      } // end lock
      args = null;
    } // end Notify
    
    /// <summary>
    /// Discover if the entity has subscribers
    /// </summary>
    /// <param name="entity">
    /// A <see cref="Entity"/>
    /// </param>
    /// <returns>
    /// A <see cref="System.Boolean"/>
    /// </returns>
    public static bool IsWatched(Entity entity)
    { return IsWatched(entity.ObjectId); }
    
    public static bool IsWatched(int objectId)
    {
      bool returnValue;
      
      if (objectId < 1)
        return false;
      lock(targetList)
      {
        returnValue = false;
        foreach(Entity entity in targetList.Keys)
        {
          if ((entity.ObjectId == objectId) &&
              (targetList[entity].Count > 0))
          {
            returnValue = true;
            break;
          }
        }
      } // end lock
      return returnValue;
    } // end IsWatched
    
    public static object GetListenerThatImplements(Whitemice.ZOGI.Entity entity, System.Type iface)
    {
      object                                      target;
      object                                      returnValue;
      
      returnValue = null;
      lock(targetList)
      {
        if (targetList.ContainsKey(entity))
        {
          foreach(EntityReference er in targetList[entity])
          {
            target = er.Target;
              if (target != null)
            {
              foreach(Type t in target.GetType().GetInterfaces())
              {
                if (t.Equals(iface))
                {
                  returnValue = target;
                  break;
                }
              }
            }
          } // end foreach
        } // end entity is watched
      } // end lock
      return returnValue;
    } // end GetListenerThatImplements
  } // end Registry
} // end namespace
